Imports Aricie.DNN.Settings
Imports Aricie.Services
Imports DotNetNuke.Services.Exceptions
Imports System.IO
Imports System.Xml
Imports DotNetNuke.Entities.Modules
Imports Aricie.DNN.Entities
Imports Aricie.DNN.Diagnostics
Imports Aricie.Collections
Imports DotNetNuke.Framework


Namespace Aricie.DNN.Services.Url.FriendlyUrl


    Public Class FriendlierUrlContext
        Inherits ContextBase(Of FriendlierUrlContext)

        Public Sub New()
        End Sub

        Public Sub New(objSettings As FriendlierUrlSettings)
            Me._FriendlierUrlPortalSettings = objSettings
        End Sub

#Region "Constants"

        Public Const NO_REWRITE As String = "norewrite"
        Public Const NO_CACHE As String = "nocache"

        Private Const BASE_REGEX As String = "(?<Scheme>\w+:\/\/)" _
                                             & "(?<MainPath>[^/\?]+(?:/(?:[^/\.\?]+(?<!tabid)(?=[/?])))*)" _
                                             & "(?<Params>" _
                                             & "(?:/tabid/(?<tabid>\d+))?" _
                                             & "(?:/" _
                                             & "(?:" _
                                             & "(?:moduleid|mid)/(?<mid>\d+)" _
                                             & "|(?:ctl/(?<ctl>[^/]+))" _
                                             & "|(?:portalid|pid)/(?<pid>\d+)" _
                                             & "|(?:language/(?<language>\w{2}\-\w{2}))" _
                                             & "|(?<Param>[^/]+/[^/\.]+)" _
                                             & ")" _
                                             & ")*" _
                                             & ")?" _
                                             & "/?" _
                                             & "(?:" _
                                             & "(?<PageName>[^\./\?]+)" _
                                             & "\.(?<Extension>[^\.\?]+)" _
                                             & ")?" _
                                             & "(?:\?(?<QueryString>.*))?"

        Public Const ALIAS_BASE_REPLACERULE_ROOT As String = "${BASE_REPLACERULE_ROOT}"
        Public Const ALIAS_BASE_REPLACERULE_END As String = "${BASE_REPLACERULE_END}"

        Public Const BASE_REPLACERULE_ROOT As String = "[${Scheme}][${UserInfo}][${RootPath}][/${Lang}][/${TabPath,SubPath}][/${TabName}][/${ModuleTitle}]"
        Public Const BASE_REPLACERULE_END As String = "[/${ControlKey}][/${Params,SubPath}][.${Extension}][?${QueryString,QueryStringValue}][#${Fragment,QueryStringFlag}]"

        Public Const EXTENSION_GROUP As String = "[.${Extension}]"


        Private Const MATCH_GROUP_REGEX As String = "(?:\\k<(?<Group>\w+)>)"

        Public Const MODULE_NAME As String = "Aricie.FriendlierUrlProvider"
        Public Const PORTAL_REGEXS As String = "Aricie.PortalRegexs"

        Public Const CACHING_DEPENDENCY As String = Aricie.Constants.Cache.Dependency & "FUP"

        Private Const glbNoRewritesCacheKey As String = "FUP.NoRewrites"
        Private Const glbPermanentRedirectsKey As String = "FUP.PermanentRedirects"

        Private Const glbHacksCacheKey As String = "FUPHacks"

        Private Const glbLookupDicoCacheKey As String = "FUP.LookupDicos"

#End Region

#Region "shared members"

        Friend Shared _LogRequestTiming As Nullable(Of Boolean)

        Friend Shared ReadOnly Property LogRequestTiming() As Boolean
            Get
                If Not _LogRequestTiming.HasValue Then
                    _LogRequestTiming = FriendlierUrlConfig.Instance.LogRequestTiming
                End If
                Return _LogRequestTiming.Value
            End Get
        End Property




        Private Shared _LockConfig As New Object
        Private Shared _Config As FriendlierUrlConfig

        Private Shared _FriendlierUrlRewriterModuleDefId As Integer = -1
        Private Shared _SharedResourceFile As String = ""

        Private Shared ReadOnly Property FriendlierUrlRewriterModuleDefId() As Integer
            Get
                If (_FriendlierUrlRewriterModuleDefId = -1) Then
                    _FriendlierUrlRewriterModuleDefId = GetModuleDefIdByModuleName(MODULE_NAME)
                End If
                Return _FriendlierUrlRewriterModuleDefId
            End Get
        End Property

#End Region


#Region "Private members"

        Private _QueryBasedNoRewrite As Boolean
        Private _NoRewrite As Boolean
        Private _Enabled As Nullable(Of Boolean)
        Private _NoCache As Boolean

        Private _PreRewriteUri As Uri

        Private _CurrentPortalSettings As DotNetNuke.Entities.Portals.PortalSettings

        Private _FriendlierUrlPortalSettings As FriendlierUrlSettings

        Private _Store As UrlStore
        Private _CurrentUrlInput As UrlRewriteInput
        Private _CurrentBehaviour As FriendlierUrlStrategy
        Private _CurrentFriendlyUrl As String = ""

        'Friend flowId As String = ""

        Private _RealFromRewritten As SerializableDictionary(Of String, String)

        Private Const OLD_CONFIG_FILE_NAME As String = "Aricie.FUP.Providers.config"

#End Region

#Region "Instance Properties"

        Private _LookupLock As New Object

        Public ReadOnly Property RealFromRewritten() As SerializableDictionary(Of String, String)
            Get
                If _RealFromRewritten Is Nothing Then

                    If Not Me.NoCache Then
                        _RealFromRewritten = CacheHelper.GetGlobal(Of SerializableDictionary(Of String, String))(glbLookupDicoCacheKey)
                    End If
                    If _RealFromRewritten Is Nothing Then
                        SyncLock _LookupLock
                            If _RealFromRewritten Is Nothing Then
                                _RealFromRewritten = Me.Store.CreateLookupDictionaries(Not FriendlierUrlConfig.Instance.OnDemandLookup)
                                SetCacheDependant(Of SerializableDictionary(Of String, String))(_RealFromRewritten, FriendlierUrlContext.CACHING_DEPENDENCY, _
                                        Constants.Cache.NoExpiration, glbLookupDicoCacheKey)
                                If FriendlierUrlConfig.Instance.LogCachedObjectCreation Then
                                    Dim objPair As New KeyValuePair(Of String, String)("Preloaded:", (Not FriendlierUrlConfig.Instance.OnDemandLookup).ToString)
                                    Dim objPair2 As New KeyValuePair(Of String, String)("Nb mappings Real From Rewritten", _RealFromRewritten.Count.ToString)
                                    Dim objDebug As New DebugInfo(Debug.DebugType, Debug.Labels.CreateLookupDictionaries, objPair, objPair2)
                                    SimpleDebugLogger.Instance.AddDebugInfo(objDebug)
                                End If
                            End If
                        End SyncLock
                    End If

                End If
                Return _RealFromRewritten
            End Get
        End Property



        Public Property Enabled() As Boolean
            Get
                If Not Me._Enabled.HasValue Then
                    'Jesse: Due to the new singleton storage of tab urls, no differentiated usage can be performed, keeping the old system for legacy cases
                    Me._Enabled = Me.FriendlierUrlPortalSettings.Enabled _
                            AndAlso (Not Me.FriendlierUrlPortalSettings.ActivateDisabledRoles OrElse Me.FriendlierUrlPortalSettings.DisablingRoles = "" _
                                OrElse (Not DnnContext.Current.IsAuthenticated) _
                                OrElse (Not DotNetNuke.Security.PortalSecurity.IsInRoles(Me.FriendlierUrlPortalSettings.DisablingRoles)))
                End If
                Return Me._Enabled.Value
            End Get
            Set(ByVal value As Boolean)
                Me._Enabled = value
            End Set
        End Property


        Public ReadOnly Property NoRewrite() As Boolean
            Get
                Return _NoRewrite
            End Get
        End Property

        Public ReadOnly Property QueryBasedNoRewrite() As Boolean
            Get
                Return _QueryBasedNoRewrite
            End Get
        End Property



        Public Property NoCache() As Boolean
            Get
                Return _NoCache
            End Get
            Set(ByVal value As Boolean)
                _NoCache = value
            End Set
        End Property


        Public Property PreRewriteUri() As Uri
            Get
                Return _PreRewriteUri
            End Get
            Set(ByVal value As Uri)
                _PreRewriteUri = value
            End Set
        End Property

        Public ReadOnly Property FriendlierUrlPortalSettings() As FriendlierUrlSettings
            Get
                If _FriendlierUrlPortalSettings Is Nothing Then
                    _FriendlierUrlPortalSettings = GetFriendlierUrlPortalSettings(NukeHelper.PortalId)
                End If
                Return _FriendlierUrlPortalSettings
            End Get
        End Property




        Public ReadOnly Property CurrentUrlInput() As UrlRewriteInput
            Get
                If _CurrentUrlInput Is Nothing Then
                    If DnnContext.HttpContext.CurrentHandler IsNot Nothing
                        if TypeOf DnnContext.HttpContext.CurrentHandler Is CDefault Then
                            _CurrentUrlInput = New UrlRewriteInput(Me, NukeHelper.PortalSettings.ActiveTab, HttpContext.Current.Request.Url, NukeHelper.PortalSettings.PortalAlias.HTTPAlias)
                        Else
                            _CurrentUrlInput = New UrlRewriteInput(Me, Nothing, HttpContext.Current.Request.Url, NukeHelper.PortalSettings.PortalAlias.HTTPAlias)
                            _CurrentUrlInput.IsNotDNNPage = True
                        End If
                    Else
                        _CurrentUrlInput = New UrlRewriteInput(Me, Nothing, HttpContext.Current.Request.Url, NukeHelper.PortalSettings.PortalAlias.HTTPAlias)
                    End If
                End If
                Return _CurrentUrlInput
            End Get
        End Property


        Public ReadOnly Property CurrentFriendlyUrl() As String
            Get
                If _CurrentFriendlyUrl = "" Then

                    _CurrentFriendlyUrl = FriendlierUrlProvider.Instance.FriendlyUrl(Me.CurrentUrlInput.Tab, Me.CurrentUrlInput.Path, Me.CurrentUrlInput.PageName, Me.CurrentUrlInput.PortalAlias)
                    If FriendlierUrlContext.LogRequestTiming Then
                        Dim objStep As New StepInfo(Debug.RequestTiming, Debug.TimingSteps.CurrentFUPEval, WorkingPhase.InProgress, False, False, -1, Me.flowId)
                        PerformanceLogger.Instance.AddDebugInfo(objStep)
                    End If

                End If
                Return _CurrentFriendlyUrl
            End Get
        End Property



        Public ReadOnly Property NoRewrites() As Dictionary(Of String, Boolean)
            Get
                Dim toReturn As SerializableDictionary(Of String, Boolean) = Nothing
                If Not Me.NoCache Then
                    toReturn = CacheHelper.GetGlobal(Of SerializableDictionary(Of String, Boolean))(glbNoRewritesCacheKey)
                End If
                If toReturn Is Nothing Then
                    If FriendlierUrlConfig.Instance.LogCachedObjectCreation Then
                        Dim objDebug As New DebugInfo(Debug.DebugType, Debug.Labels.CreateNoRewriteDictionary)
                        SimpleDebugLogger.Instance.AddDebugInfo(objDebug)
                    End If
                    toReturn = New SerializableDictionary(Of String, Boolean)
                    SetCacheDependant(Of SerializableDictionary(Of String, Boolean))(toReturn, FriendlierUrlContext.CACHING_DEPENDENCY, _
                            Constants.Cache.GlobalExpiration, glbNoRewritesCacheKey)
                End If
                Return toReturn
            End Get
        End Property

        Public ReadOnly Property PermanentRedirections() As Dictionary(Of String, String)
            Get
                Dim toReturn As SerializableDictionary(Of String, String) = Nothing
                If Not Me.NoCache Then
                    toReturn = CacheHelper.GetGlobal(Of SerializableDictionary(Of String, String))(glbPermanentRedirectsKey)
                End If
                If toReturn Is Nothing Then
                    'If Me.FriendlierUrlPortalSettings.LogCachedObjectCreation Then
                    If FriendlierUrlConfig.Instance.LogCachedObjectCreation Then
                        Dim objDebug As New DebugInfo(Debug.DebugType, Debug.Labels.CreatePermanentRedirectsDico)
                        SimpleDebugLogger.Instance.AddDebugInfo(objDebug)
                    End If
                    toReturn = New SerializableDictionary(Of String, String)
                    SetCacheDependant(Of SerializableDictionary(Of String, String))(toReturn, FriendlierUrlContext.CACHING_DEPENDENCY, _
                            Constants.Cache.GlobalExpiration, glbPermanentRedirectsKey)
                End If
                Return toReturn
            End Get
        End Property

        Public ReadOnly Property Store() As UrlStore
            Get
                If Me._Store Is Nothing Then
                    Me._Store = New UrlStore(Me)
                End If
                Return Me._Store
            End Get
        End Property

        Public Property AllowStorage As Boolean = True




#End Region

#Region "Shared Properties"

        Public Overrides Function GetInstance() As FriendlierUrlContext
            Return DnnContext.Current().GetService(Of FriendlierUrlContext)()
        End Function


        'todo: use AutoModuleConfig logic (Instance, save etc.) and delete the following
        <Obsolete> _
        Public Shared Property Config() As FriendlierUrlConfig
            Get
                'If _Config Is Nothing Then
                '    SyncLock _LockConfig
                '        If _Config Is Nothing Then
                '            'LoadConfig()
                '            _Config = FriendlierUrlConfig.Instance()
                '        End If
                '    End SyncLock
                'End If
                'Return _Config
                Return FriendlierUrlConfig.Instance
            End Get
            Set(ByVal value As FriendlierUrlConfig)
                SyncLock _LockConfig
                    'SaveConfig(value)
                    FriendlierUrlConfig.Save(value)
                End SyncLock
            End Set
        End Property

        Public Shared ReadOnly Property SharedResourceFile() As String
            Get
                If _SharedResourceFile = "" Then
                    _SharedResourceFile = GetModuleSharedResourceFile(FriendlierUrlRewriterModuleDefId)
                End If
                Return _SharedResourceFile
            End Get
        End Property


        Public Shared ReadOnly Property Hacks() As Dictionary(Of String, Regex)
            Get
                Dim toReturn As Dictionary(Of String, Regex) = _
                        GetGlobal(Of Dictionary(Of String, Regex))(glbHacksCacheKey)
                If toReturn Is Nothing Then

                    Dim pids As List(Of Integer) = Aricie.DNN.Services.NukeHelper.PortalIds

                    toReturn = New Dictionary(Of String, Regex)
                    Dim logEvent As Boolean = False
                    For Each pid As Integer In pids
                        Dim fupSettings As FriendlierUrlSettings = FriendlierUrlContext.GetFriendlierUrlPortalSettings(pid)
                        If FriendlierUrlConfig.Instance.LogCachedObjectCreation Then
                            logEvent = True
                        End If
                        For Each hack As String In fupSettings.GetHackParams
                            If Not toReturn.ContainsKey(hack) Then
                                toReturn(hack) = New Regex(Regex.Escape(hack), RegexOptions.Compiled Or RegexOptions.CultureInvariant Or RegexOptions.IgnoreCase)
                            End If
                        Next
                    Next

                    If logEvent Then
                        Dim hacksLists As String = ""
                        For Each strHack As String In toReturn.Keys
                            hacksLists = strHack & ", "
                        Next
                        hacksLists = hacksLists.TrimEnd().TrimEnd(","c)
                        Dim objPair As New KeyValuePair(Of String, String)("Hacks list", hacksLists)
                        Dim objDebug As New DebugInfo(Debug.DebugType, Debug.Labels.CreateHacks, objPair)
                        SimpleDebugLogger.Instance.AddDebugInfo(objDebug)

                    End If
                    SetCacheDependant(Of Dictionary(Of String, Regex))(toReturn, FriendlierUrlContext.CACHING_DEPENDENCY, _
                                                                           Constants.Cache.GlobalExpiration, glbHacksCacheKey)
                End If
                Return toReturn
            End Get
        End Property






#End Region


#Region "Public Methods"

        Public Shared Sub ReloadRewriters()
            _Config = Nothing
        End Sub

        Public Shared Function GetFriendlierUrlPortalSettings(ByVal portalId As Integer) As FriendlierUrlSettings
            Return GetModuleSettings(Of FriendlierUrlSettings)(SettingsScope.PortalSettings, portalId)
        End Function

        Public Sub SaveFriendlierUrlPortalSettings(portalId As Integer, settings As FriendlierUrlSettings)
            SettingsController.SetModuleSettings(Of FriendlierUrlSettings)(SettingsScope.PortalSettings, portalId, settings)
            FriendlierUrlProvider.EmptyCache()
            Me._FriendlierUrlPortalSettings = Nothing
        End Sub

        Public Sub SetNoRewrite(ByVal queryBased As Boolean, ByVal disable As Boolean)
            If queryBased Then
                _QueryBasedNoRewrite = True
            Else
                _NoRewrite = True
            End If
            If disable Then
                _Enabled = False
            End If
        End Sub

#End Region


#Region "Private methods"



        Public Shared Function GetOldConfig() As FriendlierUrlConfig
            Try
                Dim fileName As String = ""
                fileName = GetOldConfigFileName()
                If File.Exists(fileName) Then
                    Return SettingsController.LoadFileSettings(Of FriendlierUrlConfig)(fileName, False)
                End If
            Catch ex As Exception
                Aricie.Services.ExceptionHelper.LogException(ex)
            End Try
            Return New FriendlierUrlConfig
        End Function

        'Private Shared Sub SaveConfig(ByVal config As FriendlierUrlConfig)

        '    Dim fileName As String = GetConfigFileName()
        '    Aricie.DNN.Settings.SettingsController.SaveFileSettings(Of FriendlierUrlConfig)(fileName, config)


        'End Sub

        Private Shared Function GetOldConfigFileName() As String
            Return Path.Combine(HttpContext.Current.Server.MapPath(GetModuleDirectoryPath(FriendlierUrlRewriterModuleDefId)), OLD_CONFIG_FILE_NAME)

        End Function

#End Region


    End Class
End Namespace


